/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package an_semantico;

import an_lexico.Token;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Stack;

/**
 *
 * @author fernando
 */
public class AnSemantico {

    private static final int OP_INT = 0,  OP_VAR = 1,  OP_FUNCTION = 2, OP_GLOBAL=3, OP_VEC = 4;
    /**
     *  Arquivo Sintatico
     */
    PrintStream out;
    private int tempNumber;
    private Stack<Token> declaracoesTemp;
    private int ifNumber = 0;
    private int whileNumber = 0;
    private Stack<Token> whileStack;
    Stack<Token> operandosExp, operadoresExp;
    Stack<Token> declaracoesConst;
    Stack<Token> elseStack, ifEndStack;
    private int currentScopeNumber = 0;
    private static final int FIM = -2, COMP=-1,  SOMA = 0,  MULT = 1,  EXP = 2;
    private Stack<Token> definedFunctions;
    private ArrayList<String> globaisUsadas;
    private boolean isInput, isOutput;
    private int var_size;
    private Token var, ioVar;
    
    public AnSemantico() {
        varScope = new ArrayList<String>();
        operadoresExp = new Stack<Token>();
        operandosExp = new Stack<Token>();
        declaracoesConst = new Stack<Token>();
        declaracoesTemp = new Stack<Token>();
        elseStack = new Stack<Token>();
        ifEndStack = new Stack<Token>();
        whileStack = new Stack<Token>();
        definedFunctions = new Stack<Token>();
        globaisUsadas = new ArrayList<String>();
    }

    public void setOut(PrintStream out) {
        this.out = out;
    }
    /**
     * Escopo das variaveis. Cada elemento do ArrayList é um hash set de variáveis.
     */
    protected ArrayList<String> varScope;
    /**
     * Variavel que deve receber o resultado da expressão sendo calculada.
     */
    protected String assignment_var;

    /**
     * Funcões que ja foram definidas no programa fonte sendo analisado.
     */
    // protected SemanticFunction[] semanticFunctions;
    /**
     * Write the code to the output file, based only on the given semantic function and token.
     * @param semanticFunction 
     * @param t - Token
     */
    public void geraCodigo(int semanticFunction, Token t) {
        switch (semanticFunction) {
            case 0:
                break;
            case 1: // BEGIN_MAIN
                out.print("MAIN ");
                for(String s : globaisUsadas){
                    if(!varScope.contains(s)){
                        throw new Error("Variavel usada em funcao, como variavel global, mas nao declarada: "+ s);
                    }
                }
                break;
            case 2: //VAR_DECL
                var_size = 1;
                var = t;
                if (!varScope.contains(t.getID())) {
                    varScope.add(t.getID());
                } else {
                    throw new Error("Variavel Ja existe.");
                }
                break;
            case 26://VAR_SIZE
                var_size = Integer.parseInt(t.getID());
                break;
            case 25: //VAR
                if(var_size > 1){
                    for(int i =0; i<var_size; i++){
                        out.println(var.getID() + i + "_"+  currentScopeNumber  +"\t$\t=2");
                    }
                }
                else{
                out.println(var.getID() + currentScopeNumber + "\t$\t=2");
                }
                var_size = 1;
                break;
            case 3: //NOVO_ESCOPO
                currentScopeNumber++;
                if(t.getID().equals("program")){
                    currentScopeNumber = 0;
                }
                System.out.println("NOVO ESCOPO: " + t.getID());
                varScope = new ArrayList<String>();
                break;
            case 4://ASSIGNMENT_VAR
                if (!varScope.contains(t.getID())) {
                    throw new Error("Variavel nao declarada: " + t.getID());
                }
                assignment_var = t.getID();
                break;
            case 28://ASSIGN_VEC_POS
                assignment_var = assignment_var + Integer.parseInt(t.getID()) + "_";
                break;
            case 5://ASSIGNMENT_VALUE
                finalizaCodigoExpressao(t);
                out.println("\tMM\t" + assignment_var + currentScopeNumber + "");
                break;
            case 6://INT_STACK_EXP
                t.setTokenType(AnSemantico.OP_INT);
                t.setID("K" + t.getID());
                if (!declaracoesConst.contains(t)) {
                    declaracoesConst.push(t);
                }
                operandosExp.push(t);
                break;
            case 7://OPRT_STACK_EXP
                t = definePrecedencia(t);
                if (operadoresExp.empty()) {
                    operadoresExp.push(t);
                } else {
                    geraCodigoExpressao(t);
                    operadoresExp.push(t);
                }
                break;
            case 8: //PROGRAM_END
                out.println("\tHM\t/0000");
                declaraConstantes();
                declaraTemporarias();
                break;
            case 9://THEN_IF
                finalizaCodigoExpressao(t);
                ifNumber++;
                elseStack.push(new Token("ELSE", ifNumber));
                out.println("\tJZ\tELSE" + ifNumber);
                break;
            case 10://IF_SEM_ELSE
                t = elseStack.pop();
                out.println(t.getID() + t.getTokenType() + "\tJP\tIFEND" + t.getTokenType());
                ifEndStack.push(new Token("IFEND", t.getTokenType()));
            //nao usar o break!!
            case 11://IFEND
                t = ifEndStack.pop();
                out.print(t.getID() + t.getTokenType());
                break;
            case 12://ELSE
                t = elseStack.pop();
                ifEndStack.push(new Token("IFEND", t.getTokenType()));
                out.println("\tJP\tIFEND" + t.getTokenType());
                out.print(t.getID() + t.getTokenType());
                break;
            case 13://WHILE
                whileNumber++;
                out.print("WHILE" + whileNumber);
                t.setID("ENDLOOP");
                t.setTokenType(whileNumber);
                whileStack.push(t);
                break;
            case 14://LOOP
                finalizaCodigoExpressao(t);
                out.println("\tJZ\t" + whileStack.peek().getID() + whileStack.peek().getTokenType());
                break;
            case 15://END_LOOP
                t = whileStack.pop();
                out.println("\tJP\tWHILE" + t.getTokenType());
                out.print(t.getID() + t.getTokenType());
                break;
            case 16://FUNC_NAME
                if (!definedFunctions.contains(t)) {
                    definedFunctions.push(t);
                } else {
                    throw new Error("Function was defined already: " + t.getID());
                }
                break;
            case 17://BEGIN_FUNC
                out.println(definedFunctions.peek().getID() + "\tJP\t/0000");
                break;
            case 18://FUNC_RETURN
                finalizaCodigoExpressao(t);
                out.println("\tRS\t" + definedFunctions.peek().getID());
                break;
            case 19://VAR_STACK_EXP
                t.setTokenType(AnSemantico.OP_VAR);
                if (!varScope.contains(t.getID())) {
                    if (!this.definedFunctions.contains(t)) {
                        if(currentScopeNumber == 0){
                            throw new Error("Variavel/Funcao nao declarada: " + t.getID());
                        }
                        else{
                            t.setTokenType(AnSemantico.OP_GLOBAL);
                            globaisUsadas.add(t.getID());
                            t.setID(t.getID() + "0");
                        }
                    }
                }
                operandosExp.push(t);
                break;
            case 20://FUNCTION_STACK_EXP
                Token op = operandosExp.pop();
                op.setTokenType(AnSemantico.OP_FUNCTION);
                operandosExp.push(op);
                break;
            case 27://VECTOR_STACK_EXP
                Token vec = operandosExp.pop();
                vec.setTokenType(AnSemantico.OP_VAR);
                vec.setID(vec.getID()+Integer.parseInt(t.getID()) + "_");
                operandosExp.push(vec);
                break;
            case 21://INPUT
                isInput = true;
                isOutput = false;
                break;
            case 22://IO_VAR
                ioVar = t;
                break;
            case 23://IO_END
                isInput = false;
                isOutput = false;
                break;
            case 24://OUTPUT
                isInput = false;
                isOutput = true;
                break;
            case 29://GENERATE_IO
                if(isInput){
                    out.println("\tGD\t/0000");
                    out.println("\tMM\t"+ioVar.getID() + currentScopeNumber);
                }else if(isOutput){
                    out.println("\tLD\t"+ioVar.getID() + currentScopeNumber);
                    out.println("\tPD\t/0100");
                }
                break;
            case 30://INPUT_VEC
                ioVar.setID(ioVar.getID()+Integer.parseInt(t.getID()) + "_");
                break;
            default:
            //out.println(t.getID());
        }
    }

    public void initializeCode() {
        out.println("\t@\t/0000");
        out.println("\tJP\tMAIN");
    }

    private void declaraConstantes() {
        if (!declaracoesConst.empty()) {
            Token c = declaracoesConst.pop();
            while (!declaracoesConst.empty()) {
                out.println("\t" + c.getID() + "\tK\t=" + c.getID().substring(1));
                c = declaracoesConst.pop();
            }
            out.println("\t" + c.getID() + "\tK\t=" + c.getID().substring(1));
        }
    }

    private void declaraTemporarias() {
        if (!declaracoesTemp.empty()) {
            Token c = declaracoesTemp.pop();
            while (!declaracoesTemp.empty()) {
                out.println("\t" + c.getID() + "\t$\t=2");
                c = declaracoesTemp.pop();
            }
            out.println("\t" + c.getID() + "\t$\t=2");
        }
    }

    private Token definePrecedencia(Token t) {
        if (t.getID().equals("+") || t.getID().equals("-")) {
            t.setTokenType(SOMA);
        } else if (t.getID().equals("*") || t.getID().equals("/")) {
            t.setTokenType(MULT);
        } else if (t.getID().equals("^")) {
            t.setTokenType(EXP);
        } else {
            t.setTokenType(FIM);
        }
        return t;
    }

    private void finalizaCodigoExpressao(Token t) {
        t = definePrecedencia(t);
        geraCodigoExpressao(t);
        Token op = operandosExp.pop();
        if (op.getTokenType() != AnSemantico.OP_FUNCTION) {
            out.println("\tLD " + op.getID() + ((op.getTokenType() == AnSemantico.OP_VAR) ? currentScopeNumber : ""));
        } else{
            out.println("\tSC\t" + op.getID());
        }

        if (!operandosExp.empty() || !operadoresExp.empty()) {
            throw new Error("Bad Expression!!");
        }
        operandosExp = new Stack<Token>();
        operadoresExp = new Stack<Token>();
    }

    private void geraCodigoExpressao(Token t) throws UnsupportedOperationException {
        if (operadoresExp.empty()) {
            if (operandosExp.empty()) {
                throw new Error("Bad Expression");
            }
            return;
        } else {
            while (operadoresExp.peek().getTokenType() >= t.getTokenType()) {
                //Gerar Código
                Token op1;
                Token op2;
                op1 = operandosExp.pop();
                op2 = operandosExp.pop();
                if (op2.getTokenType() != AnSemantico.OP_FUNCTION) {
                    out.println("\tLD\t" + op2.getID() + ((op2.getTokenType() == AnSemantico.OP_VAR) ? currentScopeNumber : ""));
                } else {
                    out.println("\tSC\t" + op2.getID());
                }
                if (operadoresExp.peek().getTokenType() <= MULT) {
                    if (op1.getTokenType() != AnSemantico.OP_FUNCTION) {
                        out.println("\t" + operadoresExp.pop().getID() + "\t" + op1.getID() + ((op1.getTokenType() == AnSemantico.OP_VAR) ? currentScopeNumber : ""));
                    } else {
                        out.println("\tSC\t" + op1.getID());
                    }
                } else {
                    //EXPONENCIAL / PARENTESES
                    throw new UnsupportedOperationException("Not Implemented Yet");
                }
                tempNumber++;
                Token temp = new Token("TEMP" + tempNumber);
                out.println("\tMM\t" + temp.getID());

                operandosExp.push(temp);
                declaracoesTemp.push(temp);
                if (operadoresExp.empty()) {
                    break;
                }
            }
        }
    }
}
